package com.brayan.melinxa.logic.shadowers;

import java.util.ArrayList;
import java.util.List;

import com.brayan.melinxa.StatusMapper;
import com.brayan.melinxa.SystemStatus;
import com.brayan.melinxa.logic.exceptions.MonitoringException;
import com.brayan.melinxa.logic.exceptions.ShadowerInitalisationException;
import com.brayan.melinxa.logic.execution.Executor;
import com.brayan.melinxa.logic.shadowers.credentials.LoginCredentials;
import com.brayan.melinxa.logic.shadowers.locations.LocationPair;
import com.brayan.melinxa.logic.shadowers.locations.ShadowerLocation;

/**
 * Every class that is capable of shadowing from a source to a distination, must
 * implement this interface.
 * 
 * Caution, a shadower must be started explicitly, calling the start method of
 * the hosting thread - not by the constructor call!
 * 
 * Please adhere to the Open-Closed principle when adding new functionlity. Open
 * for extension, closed for change.
 * 
 * <ul>
 * <li>local directory to local directory</li>
 * <li>local directory to ftp directory</li>
 * <li>local directory to webdav directory</li>
 * <li>etc.</li>
 * </ul>
 * 
 * @author Brayan Zimmerli
 * 
 */
public abstract class Shadower implements Runnable {

	protected static Shadower shadower;
	protected Executor executor;
	protected LocationPair locationPair;

	/** Store all observers of the shadowers in here. */
	protected List<ShadowerObservable> listOfObservers = new ArrayList<ShadowerObservable>();

	/**
	 * By default every Shadower must be deactivated respectively inactive on
	 * instantiation.
	 */
	protected boolean isActivated = false;
	/**
	 * By default every Shadower must be deactivated respectively inactive on
	 * instantiation.
	 */
	protected SystemStatus state = SystemStatus.DEACTIVATED;

	public static Shadower getInstance(LocationPair locationPair)
			throws ShadowerInitalisationException {
		return null;
	}

	public static Shadower getInstance(LocationPair locationPair, LoginCredentials loginCredentials)
			throws ShadowerInitalisationException {
		return null;
	}

	// TODO later, add filters
	// public abstract Shadower getInstance(LocationPair locationPair,
	// LoginCredentials loginCredentials);

	public abstract String getTypeOfShadower();

	/**
	 * Activate this shadower in a proper way. Caution. Activate doesn't mean
	 * started (initialized). A shadower get's initially started by invoking
	 * it's run method. By design, the activated flag will be picked up by the
	 * steady-running process. If set inactive, this process will not stop, but
	 * omit to process any data.
	 * 
	 * @param activate
	 */
	public void activate(boolean activate) throws ShadowerInitalisationException {
		if (activate && isActivated()) {
			// TODO use logger
			System.out.println("Tried to activate the monitor altough running.");
			throw new ShadowerInitalisationException(
					"The data shadowing is alread running. No need to run it again.");
		}

		if (activate) {
			// TODO use logger
			System.out.println("Attempting to activate shadowing.");
			// Init throws an exception if constructor argument srcDir is
			// invalid.
			try {
				init();
				isActivated = true;
				// TODO use logger
				System.out.println("Shadowing is activated.");
			} catch (ShadowerInitalisationException sie) {
				// TODO use logger
				System.out.println(sie.getMessage());
				System.out.println("Shadowing is not activated.");
				isActivated = false;
			}
		} else {
			// TODO use logger
			System.out.println("Deactivating shadowing.");
			isActivated = false;
		}
		notifyObserversOfStateChangedTo(StatusMapper.getStatusByBoolean((Boolean) isActivated()));
	}

	/**
	 * Customize the how the Shadower is initalized.
	 * 
	 * @throws MonitoringException
	 */
	protected abstract void init() throws ShadowerInitalisationException;

	/**
	 * Gracefully shutdown the Shadower not leaving any orphans or semi-shadowed
	 * files. If you shutdown, a new thread needs to be created. In other words,
	 * you need to set (new shadower) and start this shadower again.
	 */
	public abstract void shutdown();

	/**
	 * Indicate if the shadower is ready. Having all source and destination
	 * directory, credentials, filters, etc.
	 * 
	 * @return
	 */
	public abstract boolean isReady();

	/**
	 * Indicate if this shadower is currently activated (not active).
	 * 
	 * @return boolean True if the shadower is activated.
	 */
	public abstract boolean isActivated();

	/**
	 * Set the location pair consisting of a source and destination location.
	 * 
	 * @param locationPair
	 */
	public void setLocationPair(LocationPair locationPair) {
		this.locationPair = locationPair;
	}

	/**
	 * Returns the source and the destination location encapsulated within this
	 * object.
	 * 
	 * @return LocationPair The pair of source and destination location.
	 */
	public LocationPair getLocationPair() {
		return locationPair;
	}

	/**
	 * Retrieve the custom source, which is mirrored by the shadower.
	 * 
	 * @return ShadowersSource The source that is set.
	 */
	public abstract ShadowerLocation getSource();

	/**
	 * Retrieve the shadowers custom destination.
	 * 
	 * @return ShadowersDestination The destination the shadower is currently
	 *         targeting.
	 */
	public abstract ShadowerLocation getDestination();

	/**
	 * Allow objects (explicitly GUI elements) to register themself in order to
	 * receive updates of differenc kinds.
	 * 
	 * @param ShadowingObservable
	 *            The objects which wants to observe the this shader.
	 */
	public abstract void registerObserverOfStateChange(ShadowerObservable observer);

	/**
	 * Deregister an object from the list. This observer won't receive no more
	 * updates.
	 * 
	 * @param ShadowingObservable
	 *            The object to deregister.
	 */
	public abstract void deregisterObserverOfStateChange(ShadowerObservable observer);

	/**
	 * This object is essential to update the observers about state changes of
	 * the shadower.
	 * 
	 * @param ShadowerType
	 *            The state the shadower changes to.
	 */
	public abstract void notifyObserversOfStateChangedTo(SystemStatus state);

	@Override
	public abstract void run();

}
